我的 Dokploy 迁移实战完整记录
我的 Dokploy 迁移实战完整记录
一台跑着 1Panel + OpenResty 的服务器, 一堆已经在跑的站点, 再加一个我想尝试的 Dokploy—— 故事就从端口冲突开始。
一、起点:一台已经“有历史”的服务器
在折腾 Dokploy 之前,我的服务器大概是这个状态:
- 用 1Panel 管理服务器和应用;
- 用 OpenResty(Nginx 变种) 做统一反向代理,占用
80和443端口; -
在
/opt/1panel/apps/openresty/openresty/conf/conf.d/下面,配置了一堆站点: -
wenzhixuan.com todo.626909.xyzchat.626909.xyzchat.townboats.meaim.626909.xyznewapi.626909.xyz- 以及其它一些小玩具站;
- SSL 证书由 1Panel + acme.sh + OpenResty 自动续期,已经跑得挺稳定。
我的诉求很简单:
想用 Dokploy 体验一种更“云原生”的部署方式, 最好还能顺带把这些服务都慢慢统一起来。
于是我开始了 Dokploy 安装之旅。
二、第一轮博弈:端口、OpenResty 和 Dokploy
1. Dokploy 的“强需求”:80 / 443 / 3000 必须空
Dokploy 的安装脚本非常硬核:
- 80 端口:给 Traefik 做 HTTP 入口 + ACME HTTP-01 验证
- 443 端口:给 Traefik 做 HTTPS 入口
- 3000 端口:Dokploy Web 面板
脚本在跑之前会直接检查:
ss -tulnp | grep ':80 '
ss -tulnp | grep ':443 '
ss -tulnp | grep ':3000 '
任何一个被占用就直接退出。
实际情况是:
- 80 / 443 被 OpenResty 占着;
- 3000 还被某个 docker-proxy 映射占了。
于是我进入了“先挪开旧东西”的阶段。
2. 把 OpenResty 改到 8080/8443:第一次大手术
我通过 1Panel 装的 OpenResty,没有独立的 systemd 服务名,全靠 1Panel 管理。配置路径大概是:
/opt/1panel/apps/openresty/openresty/conf/nginx.conf
/opt/1panel/apps/openresty/openresty/conf/conf.d/*.conf
我先备份了配置,然后用 sed + grep 批量改端口。途中还踩了好几次坑:
- 有的写法是
listen 80; - 有的是
listen 80 ;(多一个空格) - sed 一开始没匹配到,导致一部分配置没改成功;
- 结果就是:80 和 8080 同时被 OpenResty 监听,我以为挪干净了,其实没挪完。
通过:
grep -R "listen 80" -n /opt/1panel/apps/openresty/openresty/conf
我最后发现真正藏着 listen 80 的是 00.default.conf 这种默认配置文件。手动把它也改掉之后,再重启 OpenResty,配合:
ss -tulpn | grep -E ':80 |:443 |:8080 |:8443 '
确认:
- 80 / 443 真正空了;
- OpenResty 只监听 8080 / 8443;
- 然后干脆直接把 OpenResty 暂停,为 Dokploy 腾出所有入口端口。
那一刻是“全站彻底下线”的时刻,但也是 Dokploy 安装的窗口期。
3. Dokploy 装上去:Traefik 接管 80/443
当 ss 显示 80/443/3000 都没人用时,我执行:
curl -sSL https://dokploy.com/install.sh | sh
脚本做了这些事:
- 安装 Docker(如果没有的话);
- 初始化 Docker Swarm;
- 创建
dokploy-network; - 启动 dokploy(API + UI);
- 启动
dokploy-traefik容器,对外绑80/443。
安装完成后:
http://服务器IP:3000打开 Dokploy 面板;ss显示 80/443 被docker-proxy(Traefik)占用;- 说明 Dokploy 已经成功接管了 公网入口。
这时的状态是:
- Dokploy / Traefik:占据 80/443/3000;
- OpenResty:停着,所有网站都还处于下线状态。
三、第二轮博弈:Traefik + OpenResty 的“双重 SSL 地狱”
1. 让 OpenResty 回来做“内部服务”
我重新启动了 OpenResty,此时它只监听:
0.0.0.0:8080
0.0.0.0:8443
于是架构变成:
用户 → 80/443 (Traefik) → 8080/8443 (OpenResty) → 后端应用
理论上,只要 Traefik 把各个域名转发到 OpenResty 8080,就能恢复以前所有站点的访问。
我尝试在 Traefik 的动态配置中写一个 openresty.yaml,把多个域名都指向 http://127.0.0.1:8080,比如:
http:
routers:
todo:
rule: Host(`todo.626909.xyz`)
service: openresty
entryPoints:
- web
- websecure
tls: {}
services:
openresty:
loadBalancer:
servers:
- url: "http://127.0.0.1:8080"
2. HSTS + 双重 SSL:浏览器直接报“你的连接不是私密连接”
问题来了:
- 我之前用 1Panel + OpenResty 为这些域名申请过 正式的 Let’s Encrypt 证书;
- OpenResty 在
8443上还保持着listen 8443 ssl+ssl_certificate配置; - 这些站点还发过
Strict-Transport-Security响应头,浏览器开启了 HSTS,记住“必须 HTTPS 且证书必须合法”;
现在:
- 外层:Traefik 用自己的证书(起初是自签之类的)响应;
- 里层:OpenResty 也在做 SSL;
- 浏览器一看:证书链不对 + HSTS 还在 → 直接
NET::ERR_CERT_AUTHORITY_INVALID+ 不允许继续访问。
于是我以一句非常真实的话总结当时的心情:
“dokploy 套 openresty 双重 ssl。太麻烦了。”
说白了,这一套 Traefik → OpenResty(还在做 SSL) 的结构在概念上是正确的,但要完全调好:
- 要关掉 OpenResty 的 SSL;
- 或者让 Traefik 做 TCP 透传;
- 再和 HSTS、证书、域名解析一起调——代价太大、太容易乱。
那一刻,我做了一个关键决策:
与其维护一个 “Traefik 入口 + OpenResty 反向代理 + 双重 SSL” 的怪兽架构, 不如 逐步弃用 OpenResty,把服务迁到 Dokploy 里统一管理。
四、转向:从“反代迁移”到“业务迁移”
这时,我不再纠结“Dokploy 套 OpenResty”这种组合,而是转向一个更干脆的方案:
Dokploy 接管 80/443 → 所有新老服务,慢慢迁移成 Dokploy 管理的服务 → OpenResty 退居二线,直到可以关闭。
五、第一战:迁移 React + Vite 前端项目
目标:把一个 React + TypeScript + Vite 的前端项目迁移上 Dokploy。
1. Build 方式选择
我考虑了几种方式:
- Static:适合纯静态资源(有独立构建步骤);
- Dockerfile:最灵活,但要自己写;
- Nixpacks:自动识别项目类型,按惯例构建,省心。
最终我选了 Nixpacks,让 Dokploy 自动帮我:
- 安装 Node;
- 跑
npm install+npm run build; - 用内置的 Caddy/Nginx 之类静态服务器跑起来。
2. 502 Bad Gateway:端口搞错
首次部署完访问,浏览器给了我一个大大的 502 Bad Gateway。
排查后发现:
- 容器里服务监听的是 80;
- 我在 Dokploy 的配置里,把 容器端口/环境变量 PORT 写成了 3000;
- Traefik 试图去访问
容器:3000,结果容器根本没有这个端口开放。
修正办法:
- 在 Dokploy 面板里,把容器暴露端口改成 80;
- 或者确保项目真正监听的是 Dokploy配置中写的 port。
改完后:
前端项目顺利上线。 我第一次体验到了 “push 一下,CI/CD 完成构建 + 部署 + 域名路由 + HTTPS” 的感觉。
六、第二战:迁移 NewAPI 后端——宿主机桥接模式
第二个目标是一个旧的后端服务 NewAPI:
- 一直跑在宿主机的
4000端口; - 原本由 OpenResty 反代
/some-path到http://127.0.0.1:4000。
我不想马上把它打包成 Docker,于是选择了一个非常“云原生土办法”的方案:
用一个 Dokploy 管理的容器(socat)做桥接: Traefik → socat(80)→ 宿主机 4000。
1. 核心思路:socat 转发
我在 Dokploy 中创建一个 Compose 服务,跑 alpine/socat:
services:
newapi-bridge:
image: alpine/socat
command: >
socat TCP-LISTEN:80,fork,reuseaddr TCP:host.docker.internal:4000
extra_hosts:
- "host.docker.internal:host-gateway"
这意味着:
- 容器内部 80 端口接收来自 Traefik 的流量;
- socat 把收到的流量转发到
host.docker.internal:4000(宿主机上 NewAPI 的端口); extra_hosts让容器里的host.docker.internal指向宿主机。
2. 排错:404 / 连接失败 / 搞混了“容器看见的世界”和“宿主机看见的世界”
我曾误判过几次:
- 在 宿主机 上敲
curl http://host.docker.internal:4000,发现不通,于是以为“端口不通”; - 实际上,这个域名本来就是 容器内用的,宿主机当然不认识;
-
真正有用的测试是:
-
在容器里
curl http://host.docker.internal:4000; - 在宿主机上确认
NewAPI监听的是0.0.0.0:4000而不是127.0.0.1:4000。
后来又踩了一次坑:
- socat 容器里监听的是
80; - 我在 Dokploy 的域名路由里,却把“Target port”/“Routing port” 填成了 3000 或其他;
- Traefik 根本没打到容器的 80。
最终修正:
- Dokploy 域名配置里,端口改为 80;
- 重启容器确保
extra_hosts生效; - 确保
NewAPI服务监听的是0.0.0.0:4000。
最后总结这条链路:
用户 → Traefik (443) → socat 容器 (80) → 宿主机 NewAPI (4000)
同时我也彻底理解了:
- “环境变量里的
PORT是给应用看”的; - “Dokploy / Traefik 的端口配置是给代理路由看的”;
- 两者完全是两套东西,不要搞混。
七、第三战:Todo 后端与路径反代 vs 子域名
第三个后端服务是 Todo:
- 旧架构里跑在宿主机
8000; - 通过 OpenResty 用路径方式暴露,比如
todo.626909.xyz/api/。
迁移到 Dokploy 后,我意识到:
- 继续玩“路径级反代”(
/api)会迫使我在 Traefik 上写更复杂的规则(PathPrefix、StripPrefix 等); - 在云原生时代,更推荐一个服务一个子域名的方式。
于是我做了一个架构上的决定:
不再用
/api路径暴露后端, 而是用新的子域名:api.todo.626909.xyz。
迁移计划也很清晰:
- 后端服务:继续跑在宿主机
8000; - Dokploy 再复制一份
socat桥接配置,从4000改为8000; - 绑定一个新的子域名
api.todo.626909.xyz,指向这个 bridge 服务; -
在前端 React 代码中:
-
把原来的
fetch('/api/...') - 改为
fetch('https://api.todo.626909.xyz/...') - 重新构建+部署。
这一步完成之后:
Todo 项目前后端就会全部脱离原 OpenResty, 完整挂在 Dokploy / Traefik 体系之下。
八、现状与收官:我真正完成了什么?
截至目前,我完成/规划的是:
- ✅ Dokploy 已经接管 80/443/3000,成为服务器的统一入口;
-
✅ OpenResty 已经从“公网入口”退化为“可有可无”:
-
80/443 不再由它监听;
- 我有计划逐步把所有站都从它那里迁走;
- ✅ 前端(React + Vite)已经在 Dokploy 内跑起来,构建 & 部署都交给 Nixpacks;
- ✅ NewAPI 后端已经通过 socat bridge 接入 Dokploy;
- 🟡 Todo 后端正在规划用
api.todo.626909.xyz的方式接入; -
🟥 双层 SSL/HSTS 的坑已认识清楚:
-
未来要么完全让 Traefik 管证书;
- 要么就别再让 OpenResty 抢 HTTPS 入口。
九、这次实战,我学会了这些“底层认知”
-
Ingress / 入口网关只有一个 80/443 最好只由一个组件统一管理: 要么 OpenResty/Nginx,要么 Traefik/Caddy,混搭很容易出事。
-
SSL / 证书终止点只能有一个 双重 SSL 不但没必要,还会和 HSTS 搞出一堆奇怪问题。 要么在 Traefik 终止 TLS,要么在 OpenResty 终止,不能两个都上。
-
应用端口 vs 代理端口是两回事
-
PORT环境变量是应用用来监听的端口; - Traefik/Dokploy 的“Routing Port”是入口网关用来连过去的端口;
-
两者不一致就必挂:程序听 80,你路由去 3000,一定挂。
-
容器和宿主机看到的世界不一样
-
宿主机上的
localhost≠ 容器里的localhost; host.docker.internal是“容器眼里的宿主机”;-
想让容器访问宿主机端口,要么监听 0.0.0.0,要么配
extra_hosts。 -
与其死扛旧架构,不如接受新入口,逐步迁移业务 一开始我试图让 Dokploy “套” OpenResty,事实证明维护成本和心智负担都偏高。 换个思路,用 Dokploy 作为统一入口,把业务一点点迁进去,思路反而更清晰。